package cc.rengu.igas.share.core.realize.impl;

import cc.rengu.igas.share.core.model.GeetestResult;
import cc.rengu.oltp.service.common.constant.AppParamConstant;
import cc.rengu.oltp.service.common.entity.SysParam;
import cc.rengu.oltp.service.model.ImageCodeResult;
import cc.rengu.oltp.service.realize.ImageCodeService;
import cc.rengu.oltp.service.realize.SysParamService;
import cc.rengu.oltp.service.realize.impl.SysParamServiceImpl;
import cc.rengu.utility.log.RgLog;
import cc.rengu.utility.log.RgLogger;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;

/**
 * Created by wangcheng on 2021/3/25 11:46
 */
public class GeetestImageCodeImpl implements ImageCodeService {
    private final RgLogger rglog = RgLog.getLogger(this.getClass().getName());
    private static final String IMAGE_CODE_PARAM = "IMAGE_CODE_PARAM";
    private static final String IMAGE_CODE_CID = "cId";
    private static final String IMAGE_CODE_CKEY = "cKey";
    private static final String IMAGE_CODE_TOKENKEY = "tokenKey";
    private static final String IMAGE_CODE_REGISTER_URL = "registerUrl";
    private static final String IMAGE_CODE_VALIDATE_URL = "validateUrl";
    private static final String FAILBACK_ANSWER_PATH = "/ajax.php";
    private static final String FAILBACK_QUESTION_PATH = "/get.php";
    private static final String FAILBACK_FULLPAGE_JS = "static/js/fullpage.shandong.0.0.0.js";
    private static final String FAILBACK_CSS_WIND = "static/css/wind/style.0.0.0.css";
    private static final String FAILBACK_DYNAMIC_REQ_DOMAIN = "127.0.0.1:8080";
    private static final String FAILBACK_STATIC_REQ_DOMAIN = "127.0.0.1:8080";
    private static final String FAILBACK_TYPE = "fullpage";

    @Override
    public ImageCodeResult applyImageCode(String instId, String applySeqNum, Object imageApplyInfo) throws Exception {
        String riskType = (String) imageApplyInfo;
        GeetestResult geetestResult = register(instId, applySeqNum, riskType);
        if (null != geetestResult && "0".equals(geetestResult.getStatus())) {
            ImageCodeResult imageCodeResult = new ImageCodeResult();
            imageCodeResult.setImageType("geetest");
            imageCodeResult.setApplySeqNum(applySeqNum);
            imageCodeResult.setResult(geetestResult);
            return imageCodeResult;
        }
        return null;
    }

    @Override
    public boolean verifyImageCode(String instId, String verifySeqNum, Object imageVerifyInfo) throws Exception {
        return validate(instId, verifySeqNum);
    }

    private GeetestResult register(String instId, String sysSeqNum, String riskType) throws Exception {
        GeetestResult geetestResult = new GeetestResult();
        HashMap<String, String> paramMap = new HashMap<>();
        SysParamService sysParamService = new SysParamServiceImpl();
        SysParam sysParam = sysParamService.getSysParamInfo(instId, IMAGE_CODE_PARAM, IMAGE_CODE_CID);
        if (null == sysParam) {
            rglog.error("法人行未配置图片验证码CID");
            return null;
        }
        geetestResult.setCid(sysParam.getParamValue().trim());
        sysParam = sysParamService.getSysParamInfo(instId, IMAGE_CODE_PARAM, IMAGE_CODE_CKEY);
        if (null == sysParam) {
            rglog.error("法人行未配置图片验证码CKEY");
            return null;
        }
        String cKey = sysParam.getParamValue().trim();
        sysParam = sysParamService.getSysParamInfo(AppParamConstant.DEFAULT_INSTID, IMAGE_CODE_PARAM, IMAGE_CODE_TOKENKEY);
        if (null == sysParam) {
            rglog.error("法人行未配置图片验证码TOKENKEY");
            return null;
        }
        String tokenKey = sysParam.getParamValue().trim();
        sysParam = sysParamService.getSysParamInfo(AppParamConstant.DEFAULT_INSTID, IMAGE_CODE_PARAM, IMAGE_CODE_REGISTER_URL);
        if (null == sysParam) {
            rglog.error("法人行未配置图片验证码registerUrl");
            return null;
        }
        String registerUrl = sysParam.getParamValue().trim();
        /* 银行编码号 */
        paramMap.put("identifier", instId);
        /* 控制验证类型：clik,slide,fullpage分别代表二层验证弹出点选验证、滑动验证、一点即过 */
        if (null == riskType || riskType.isEmpty()) {
            paramMap.put("risk_type", "slide");
        } else {
            paramMap.put("risk_type", riskType);
        }
        /* 银行业务全局交易流水号 */
        paramMap.put("global_trace_number", sysSeqNum);
        String responseStr = requestRegister(paramMap, geetestResult.getCid(), cKey, tokenKey, registerUrl);
        if (responseStr.isEmpty()) {
            geetestResult.setStatus("1");
            geetestResult.setMsg("请求极验register接口失败，后续流程走failback模式");
            buildFailRegisterResult(geetestResult);
        } else {
            JSONObject responseJson = JSON.parseObject(responseStr);
            int status = responseJson.getIntValue("status");
            if (status == 0) {
                geetestResult.setStatus("0");
                buildSuccRegisterResult(geetestResult, responseJson);
            } else {
                geetestResult.setStatus("1");
                geetestResult.setMsg("请求极验register接口失败，后续流程走failback模式");
                buildFailRegisterResult(geetestResult);
            }
        }
        return geetestResult;
    }

    /**
     * 图型验证码二次验证
     *
     * @param instId    多法人标识
     * @param sysSeqNum 验证流水号
     * @return true-验证通过,false-验证失败
     */
    private boolean validate(String instId, String sysSeqNum) throws Exception {
        SysParamService sysParamService = new SysParamServiceImpl();
        SysParam sysParam = sysParamService.getSysParamInfo(instId, IMAGE_CODE_PARAM, IMAGE_CODE_CID);
        if (null == sysParam) {
            rglog.error("法人行未配置图片验证码CID");
            return false;
        }
        String cId = sysParam.getParamValue().trim();
        sysParam = sysParamService.getSysParamInfo(instId, IMAGE_CODE_PARAM, IMAGE_CODE_CKEY);
        if (null == sysParam) {
            rglog.error("法人行未配置图片验证码CKEY");
            return false;
        }
        String cKey = sysParam.getParamValue().trim();
        sysParam = sysParamService.getSysParamInfo(AppParamConstant.DEFAULT_INSTID, IMAGE_CODE_PARAM, IMAGE_CODE_TOKENKEY);
        if (null == sysParam) {
            rglog.error("法人行未配置图片验证码TOKENKEY");
            return false;
        }
        String tokenKey = sysParam.getParamValue().trim();
        sysParam = sysParamService.getSysParamInfo(AppParamConstant.DEFAULT_INSTID, IMAGE_CODE_PARAM, IMAGE_CODE_VALIDATE_URL);
        if (null == sysParam) {
            rglog.error("法人行未配置图片验证码registerUrl");
            return false;
        }
        String validateUrl = sysParam.getParamValue().trim();
        String responseStr = requestValidate(sysSeqNum, null, cId, cKey, tokenKey, validateUrl);
        if (responseStr.isEmpty()) {
            rglog.error("图片验证接口失败！");
            return false;
        } else {
            JSONObject responseJson = JSON.parseObject(responseStr);
            String result = (String) responseJson.getJSONObject("data").get("result");
            if (null == result || result.isEmpty() || !"success".equals(result)) {
                rglog.error("图片验证接口调用结果:{}", responseStr);
                return false;
            }
        }
        return true;
    }

    /**
     * 向极验发送一次验证的请求，POST方式
     *
     * @param paramMap 请求参数
     * @return 响应数据，字符串形式
     */
    private String requestRegister(HashMap<String, String> paramMap, String cId, String cKey, String tokenKey, String registerUrl) {
        try {
            String timestamp = String.valueOf(new Date().getTime() / 1000);
            String token_str = String.format("%s%s%s", cKey, tokenKey, timestamp);
            String token = sha256Encode(token_str);
            JSONObject paramJson = new JSONObject();
            paramJson.put("timestamp", timestamp);
            paramJson.put("token", token);
            paramJson.put("cid", cId);
            for (String key : paramMap.keySet()) {
                if (key == null || key.isEmpty() || paramMap.get(key) == null || paramMap.get(key).isEmpty()) {
                    continue;
                }
                paramJson.put(key, URLEncoder.encode(paramMap.get(key), "utf-8"));
            }
            String responseStr = readContentFromPost(registerUrl, paramJson.toString());
            if ("fail".equals(responseStr)) {
                rglog.error("一次验证请求失败，将进入failback模式,返回值:{}", responseStr);
                return "";
            } else {
                rglog.debug("一次验证请求正常，将进入成功处理模式,返回值:{}", responseStr);
                return responseStr;
            }
        } catch (Exception e) {
            rglog.error("一次验证发生异常:", e.toString());
        }
        return "";
    }


    /**
     * 向极验发送二次验证的请求，POST方式
     *
     * @return 响应数据，字符串形式
     */
    private String requestValidate(String challenge, HashMap<String, String> paramMap, String cId, String cKey, String tokenKey, String validateUrl) {
        try {
            String timestamp = String.valueOf(new Date().getTime() / 1000);
            String token_str = String.format("%s%s%s", cKey, tokenKey, timestamp);
            String token = sha256Encode(token_str);
            JSONObject paramJson = new JSONObject();
            paramJson.put("challenge", challenge);
            paramJson.put("cid", cId);
            paramJson.put("timestamp", timestamp);
            paramJson.put("token", token);
            if (null != paramMap) {
                for (String key : paramMap.keySet()) {
                    if (key == null || key.isEmpty() || paramMap.get(key) == null || paramMap.get(key).isEmpty()) {
                        continue;
                    }
                    paramJson.put(key, URLEncoder.encode(paramMap.get(key), "utf-8"));
                }
            }

            rglog.debug("极验请求数据：validateUrl:<{}>, data:<{}>", validateUrl, JSON.toJSONString(paramJson));

            String responseStr = readContentFromPost(validateUrl, paramJson.toString());
            if ("fail".equals(responseStr)) {
                rglog.error("二次验证请求失败，返回值:{}", responseStr);
                return "";
            } else {
                rglog.debug("二次验证正常,返回值:{}", responseStr);
                return responseStr;
            }
        } catch (Exception e) {
            rglog.error("二次验证请求发生异常:{}", e.toString());
        }
        return "";
    }


    /**
     * 发送POST请求，获取服务器返回结果
     *
     * @return 服务器返回结果
     */
    private String readContentFromPost(String URL, String data) {
        HttpURLConnection connection = null;
        OutputStreamWriter outputStreamWriter = null;
        InputStream inStream = null;
        try {
            java.net.URL postUrl = new URL(URL);
            connection = (HttpURLConnection) postUrl.openConnection();

            /* 设置连接主机超时（单位：毫秒）*/
            connection.setConnectTimeout(2000);
            /* 设置从主机读取数据超时（单位：毫秒）*/
            connection.setReadTimeout(2000);
            connection.setRequestMethod("POST");
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            /* 建立与服务器的连接，并未发送数据 */
            connection.connect();
            outputStreamWriter = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8);
            outputStreamWriter.write(data);
            outputStreamWriter.flush();
            outputStreamWriter.close();
            if (connection.getResponseCode() == 200) {
                /* 发送数据到服务器并使用Reader读取返回的数据 */
                StringBuilder sBuffer = new StringBuilder();

                byte[] buf = new byte[1024];
                inStream = connection.getInputStream();
                for (int n; (n = inStream.read(buf)) != -1; ) {
                    sBuffer.append(new String(buf, 0, n, StandardCharsets.UTF_8));
                }
                inStream.close();
                connection.disconnect();
                return sBuffer.toString();
            }
        } catch (Exception e) {
            rglog.error("发送POST请求，发生异常:", e.toString());

        } finally {
            if (null != connection) {
                connection.disconnect();
            }
            if (null != outputStreamWriter) {
                try {
                    outputStreamWriter.close();
                } catch (IOException ignored) {
                }
            }
            if (null != inStream) {
                try {
                    inStream.close();
                } catch (IOException ignored) {
                }
            }
        }
        return "fail";
    }

    /**
     * 构造成功请求结果
     *
     * @param geetestResult 成功返回结果
     * @param responseJson  图型验证码服务器返回结果
     */
    private void buildSuccRegisterResult(GeetestResult geetestResult, JSONObject responseJson) {
        geetestResult.setChallenge(responseJson.getJSONObject("data").getString("challenge"));
        geetestResult.setType(responseJson.getJSONObject("data").getString("type"));
        geetestResult.setStaticReqDomain(responseJson.getJSONObject("data").getString("static_req_domain"));
        geetestResult.setFullpage(responseJson.getJSONObject("data").getString("fullpage"));
        geetestResult.setClick(responseJson.getJSONObject("data").getString("click"));
        geetestResult.setSlide(responseJson.getJSONObject("data").getString("slide"));
        geetestResult.setDynamicReqDomain(responseJson.getJSONObject("data").getString("dynamic_req_domain"));
        geetestResult.setQuestionPath(responseJson.getJSONObject("data").getString("question_path"));
        geetestResult.setAnswerPath(responseJson.getJSONObject("data").getString("answer_path"));
    }

    /**
     * 构造失败请求结果
     *
     * @param geetestResult 失败请求结果
     */
    private void buildFailRegisterResult(GeetestResult geetestResult) {
        String challenge = sha256Encode(Math.round(new SecureRandom().nextDouble() * 100) + "");
        geetestResult.setChallenge(challenge);
        geetestResult.setOffline(true);
        geetestResult.setType(FAILBACK_TYPE);
        geetestResult.setFullpage(FAILBACK_FULLPAGE_JS);
        geetestResult.setCss(FAILBACK_CSS_WIND);
        geetestResult.setDynamicReqDomain(FAILBACK_DYNAMIC_REQ_DOMAIN);
        geetestResult.setStaticReqDomain(FAILBACK_STATIC_REQ_DOMAIN);
        geetestResult.setAnswerPath(FAILBACK_ANSWER_PATH);
        geetestResult.setQuestionPath(FAILBACK_QUESTION_PATH);
    }

    /**
     * 利用java原生的摘要实现SHA256加密
     */
    private String sha256Encode(String str) {
        MessageDigest messageDigest;
        String encodeStr = "";
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
            encodeStr = this.byte2Hex(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            rglog.error(e.getMessage());
        }
        return encodeStr;
    }

    /**
     * 将byte转为16进制
     */
    private String byte2Hex(byte[] bytes) {
        StringBuilder stringBuffer = new StringBuilder();
        String temp;
        for (byte aByte : bytes) {
            temp = Integer.toHexString(aByte & 0xFF);
            if (temp.length() == 1) {
                //1得到一位的进行补0操作
                stringBuffer.append("0");
            }
            stringBuffer.append(temp);
        }
        return stringBuffer.substring(0, 32);
    }
}
